//======================================================================================
/** \file  avr_adc.cpp
 *    This file contains a very, very simple A/D converter driver. WARNING: It has bugs!
 *    It's only here as an example. It should not be used in a meaningful project. 
 *
 *  Revisions:
 *    \li 01-15-2008 JRR Original (somewhat useful) file
 *    \li 01-15-2008 JRR Changed to new file/directory layout with ./lib and *.cpp
 *
 *  License:
 *    This file released under the Lesser GNU Public License. The program is intended
 *    for educational use only, but its use is not restricted thereto. 
 */
//======================================================================================

#include <stdlib.h>							// Include standard library header files
#include <avr/io.h>
#include "rs232int.h"						// Include header for serial port class
#include "avr_adc.h"						// Include header for the A/D class

#define ADC_PRESCALE		0x05			///< Default prescaler setting (F_cpu / 64)
#define ADC_RETRIES			10000			///< Retries before giving up on conversion


//-------------------------------------------------------------------------------------
/** This union holds the two bytes which are read from the A/D converter's result
 *  registers after a conversion has been completed. They're referenced also as one
 *  16-bit integer which can be returned as the result of a conversion.
 */

union ADC_result
{
	int word;								///< The whole 16-bit number
	char bytes[2];							///< The bytes in the number
};


//-------------------------------------------------------------------------------------
/** This constructor sets up an A/D converter. How does it do so?
 *  @param p_serial_port A pointer to the serial port which writes debugging info. 
 */

avr_adc::avr_adc (base_text_serial* p_serial_port)
    {
    ptr_to_serial = p_serial_port;          // Store the serial port pointer locally

    ADMUX = 0;                              // Set reference to AVCC pin voltage
    ADCSRA = (1 << ADEN) | ADC_PRESCALE;    // Set clock prescaler and turn A/D on
    }


//-------------------------------------------------------------------------------------
/** This method takes one A/D reading from the given channel, and...does something...
 *  \param  channel The A/D channel which is being read must be from 0 to 7
 *  \return The result of the A/D conversion, or 0xFFFF if there was a timeout
 */

unsigned int avr_adc::read_once (unsigned char channel)
    {
    unsigned int counter;                   // Counter waits for timeouts
    ADC_result what_read;                   // Puts together result read from A/D

    // Set the channel
    ADMUX &= 0b11111000;                    // Clear the channel bits
    ADMUX |= (channel & 0b00000111);        // Put channel bits in the register

    // Start a conversion
    ADCSRA |= (1 << ADSC);

    // Wait until a timeout, or the conversion is finished
    for (counter = 0; ADCSRA & (1 << ADSC); counter++)
        {
        if (counter > ADC_RETRIES)
            return (0xFFFF);
        }

    what_read.bytes[0] = ADCL;
    what_read.bytes[1] = ADCH & 0b00000011;

    // There was no timeout, but we didn't get any reading either, so return 0
    return (what_read.word);
    }
